/* bzflag
 * Copyright (c) 1993-2020 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/* interface header */
#include "CommandManager.h"

/* system implementation headers */
#include <ctype.h>
#include <stdio.h>
#include <assert.h>
#include <string>

/* common implementation headers */
#include "TextUtils.h"


void                CommandManager::add(const std::string& name,
                                        CommandFunction func,
                                        const std::string& help)
{
    commands.erase(name);
    CmdInfo info;
    info.func = func;
    info.help = help;
    commands.insert(std::make_pair(name, info));
}

void                CommandManager::remove(const std::string& name)
{
    commands.erase(name);
}

std::string         CommandManager::getHelp(const std::string& name) const
{
    // look up command
    Commands::const_iterator index = commands.find(name);
    if (index == commands.end())
        return "";

    // return help string
    return index->second.help;
}

std::string         CommandManager::run(const std::string& name,
                                        const ArgList& args, bool* ret) const
{
    // look up command
    Commands::const_iterator index = commands.find(name);
    if (index == commands.end())
    {
        return TextUtils::format("Command %s not found", name.c_str());
        if (ret)
            *ret = false;
    }
    if (ret)
        *ret = true;
    // run it
    return (*index->second.func)(name, args,ret);
}

std::string         CommandManager::run(const std::string& cmd,bool *ret) const
{
    std::string result;
    const char* scan = cmd.c_str();

    scan = skipWhitespace(scan);
    while (scan != NULL && *scan != '\0')
    {
        std::string name;
        ArgList args;

        // parse command name
        scan = skipWhitespace(scan);
        scan = readValue(scan, &name);
        if (scan != NULL)
            scan = skipWhitespace(scan);

        // parse arguments
        while (scan != NULL && *scan != '\0' && *scan != ';')
        {
            std::string value;
            scan = readValue(scan, &value);
            if (scan != NULL)
            {
                scan = skipWhitespace(scan);
                args.push_back(value);
            }
        }

        // run it or report error
        if (scan == NULL)
        {
            if (ret)
                *ret = false;
            return std::string("Error parsing command");
        }
        else if (name[0] != '#')
            result = run(name, args,ret);

        // discard ; and empty commands
        while (scan != NULL && *scan == ';')
        {
            ++scan;
            scan = skipWhitespace(scan);
        }
    }

    // return result of last command only
    return result;
}

void                CommandManager::iterate(Callback callback,
        void* userData) const
{
    assert(callback != NULL);

    for (Commands::const_iterator index = commands.begin();
            index != commands.end(); ++index)
        (*callback)(index->first, userData);
}


const char*         CommandManager::readValue(const char* string,
        std::string* value)
{
    if (*string == '\"')
        return readQuoted(string + 1, value);
    else if (*string != '\0')
        return readUnquoted(string, value);
    else
        return string;
}

const char*         CommandManager::readUnquoted(const char* string,
        std::string* value)
{
    // read up to next whitespace.  escapes are not interpreted.
    const char* start = string;
    while (*string != '\0' && !isspace(*string) && *string != ';')
        ++string;
    *value = std::string(start, string - start);
    return string;
}

const char*         CommandManager::readQuoted(const char* string,
        std::string* value)
{
    *value = "";
    bool escaped = false;
    for (; *string != '\0'; ++string)
    {
        if (escaped)
        {
            switch (*string)
            {
            case 't':
                value->append("\t", 1);
                break;
            case 'n':
                value->append("\n", 1);
                break;
            case 'r':
                value->append("\r", 1);
                break;
            case '\\':
                value->append("\\", 1);
                break;
            case '\"':
                value->append("\"", 1);
                break;
            default:
                value->append(string, 1);
                break;
            }
            escaped = false;
        }
        else if (*string == '\\')
            escaped = true;
        else if (*string == '\"')
            return string + 1;
        else
            value->append(string, 1);
    }
    // closing quote is missing.  if escaped is true the called may have
    // wanted to continue the line but we don't allow that.
    return NULL;
}

const char*         CommandManager::skipWhitespace(const char* string)
{
    while (*string != '\0' && isspace(*string))
        ++string;
    return string;
}

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
